﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace Eagle.Module
{
    /// <summary>
    /// 验证码
    /// </summary>
    public class VerifyCode
    {
        readonly static char[] characterTable = 
        {   
            '3','4','5','6','7','8','9',
            'A'    ,'C','D',    'F',
            'H','K','L'    ,'N',
                    'P',    'R','S','T',
            'U','V',        'X','Y'
        };
        readonly static Random rnd = new Random();
        readonly static double[,] waveMatrix;
        readonly static Size waveMatrixSize;
        readonly static Color[] colors = new Color[] { Color.Black, Color.Red, Color.Green, Color.SkyBlue, Color.DarkBlue, Color.SlateGray, Color.DarkKhaki };
        readonly static Bitmap oneMap = new Bitmap(1, 1);
        readonly static Graphics oneG = Graphics.FromImage(oneMap);
        readonly static Color emptyColor = Color.FromArgb(0, 0, 0, 0);

        static VerifyCode()
        {
            waveMatrixSize = new Size(500, 500);
            waveMatrix = new double[waveMatrixSize.Width, waveMatrixSize.Height];

            for (int i = 0; i < waveMatrixSize.Width; i++)
            {
                for (int j = 0; j < waveMatrixSize.Height; j++)
                {
                    waveMatrix[i, j] = (Math.Sin((i + j) * Math.PI / 70.0) + 1.0) / 2.0;
                }
            }
        }
        /// <summary>
        /// 得到将要绘制验证码图片的字符串
        /// </summary>
        /// <param name="count">要生成的字符个数</param>
        /// <returns></returns>
        public static string NextCode(int count)
        {
            string checkCode = string.Empty;
            for (int i = 0; i < count; i++)
            {
                checkCode += characterTable[rnd.Next(characterTable.Length)];
            }
            return checkCode;
        }
        /// <summary>
        /// 绘制验证码
        /// </summary>
        /// <param name="code">要绘制的字符串</param>
        /// <returns></returns>
        public static Bitmap NextImage(string code)
        {
            return NextImage(code, null, 30, 1);
        }
        /// <summary>
        /// 绘制验证码
        /// </summary>
        /// <param name="code">要绘制的字符串</param>
        /// <param name="background">是否有背景图</param>
        /// <param name="emSize">字体大小</param>
        /// <param name="range">正弦变换范围大小，1--表示不需要变换，>1--表示需要变换</param>
        /// <returns></returns>
        public static Bitmap NextImage(string code, Image background, int emSize, int range)
        {
            if (emSize < 15)
                throw new ArgumentOutOfRangeException("emSize", "emSize<15");
            if (range < 1)
                throw new ArgumentOutOfRangeException("range", "range<1");

            using (Font font = new Font("Calibri", emSize, (FontStyle.Bold | FontStyle.Italic)))
            {
                using (Pen pen = new Pen(colors[rnd.Next(colors.Length)]))
                {
                    Size fontSize = ExtensionG.MeasureStringA(oneG, code, font).ToSize();
                    float spacing = (float)-Math.Sqrt(1.4 * emSize);

                    fontSize.Width += range + (int)(spacing * (code.Length - 3));
                    fontSize.Height += range;

                    Bitmap bmp = new Bitmap(fontSize.Width, fontSize.Height);

                    using (Graphics g = Graphics.FromImage(bmp))
                    {
                        g.Clear(Color.White);
                        g.SmoothingMode = SmoothingMode.AntiAlias;
                        ExtensionG.DrawStringA(g, code, font, pen.Brush, 0f, 0f, spacing);
                        pen.Width = 2;
                        g.DrawCurve(pen, new Point[] {
                    new Point(0, rnd.Next(fontSize.Height)),
                    new Point(fontSize.Width >> 1, rnd.Next(fontSize.Height >> 2, fontSize.Height >> 1)),
                    new Point(fontSize.Width , rnd.Next(fontSize.Height)) });
                        if (range > 1)
                            bmp = WaveBitmap(bmp, range);
                        if (background != null)
                            bmp = DrawBackground(bmp, background);
                        return bmp;
                    }
                }
            }
        }

        private static Bitmap WaveBitmap(Bitmap org, int range)
        {
            Bitmap bmp = new Bitmap(org.Width, org.Height);

            int xi = rnd.Next(waveMatrixSize.Width - org.Width);
            int xj = rnd.Next(waveMatrixSize.Height - org.Height);
            int yi = rnd.Next(waveMatrixSize.Width - org.Width);
            int yj = rnd.Next(waveMatrixSize.Height - org.Height);

            for (int i = 0; i < org.Width; i++, xi++, yi++, xj -= org.Height, yj -= org.Height)
            {
                for (int j = 0; j < org.Height; j++, xj++, yj++)
                {
                    int nx = i - (int)(waveMatrix[xi, xj] * range);
                    int ny = j - (int)(waveMatrix[yi, yj] * range);
                    if (0 <= nx && 0 <= ny)
                    {
                        Color c = org.GetPixel(nx, ny);
                        if (c != emptyColor)
                            bmp.SetPixel(i, j, c);
                    }
                }
            }
            return bmp;
        }

        private static Bitmap DrawBackground(Bitmap org, Image background)
        {
            Bitmap bmp = new Bitmap(background, org.Size);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.DrawImage(org, 0, 0);
            }
            return bmp;
        }
    }

    internal static class ExtensionG
    {
        readonly static StringFormat measureSF;

        static ExtensionG()
        {
            measureSF = StringFormat.GenericTypographic;
            measureSF.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
        }

        /// <summary>
        /// 在制定位置画出制定样式的可调间距的文字
        /// </summary>
        /// <param name="g">Graphics</param>
        /// <param name="s">要画的文字</param>
        /// <param name="font">字体</param>
        /// <param name="brush">刷子</param>
        /// <param name="x">x坐标</param>
        /// <param name="y">y坐标</param>
        /// <param name="spacing">间距，可以是负值</param>
        /// <exception cref="System.ArgumentNullException"></exception>
        public static void DrawStringA(Graphics g, string s, Font font, Brush brush, float x, float y, float spacing)
        {
            foreach (char c in s)
            {
                g.DrawString(c.ToString(), font, brush, x, y);
                x += MeasureStringA(g, c.ToString(), font).Width + spacing;
            }
        }

        public static SizeF MeasureStringA(Graphics g, string text, Font font)
        {
            return g.MeasureString(text, font, 0, measureSF);
        }
    }
}
